前言
2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧!
在物件導向的程式語言當中常常回看到 this 的出現,通常這個 this 會指向物件本身,譬如
const obj = {
value: 18,
print: function () {
console.log(this.value)
}
}
obj.print() // 18
但是,好像不是每次使用 this 的時候都能如願以償,如果把上面的 print function 改成 arrow function,就沒辦法得到一樣的值
const obj = {
value: 18,
print: () => {
console.log(this.value)
}
}
obj.print() // undefined
所以想趁這個機會,再次釐清一下在 JavaScript 當中關於 this 的用法。不過關於 this 的用法,網路上已經有許多的討論,特別是 Huli 的 淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂,內容相當的豐富。所以今天我想反過來,直接從規則和案例出發,幫助自己在實作的時候更能夠上手。
當然,還是建議大家有機會能閱讀相關文章當中的深入討論。
以下的例子都是在瀏覽器中執行:
const obj = {
value: 18,
print: function () {
console.log(this)
}
}
let fn = obj.print
console.log(fn) // [Function: printer]
obj.print() // { value: 18, print: [Function: printer] }
fn() // window
在這裡我們建立了一個變數 fn
來指向obj.print
這個 function,看起來 fn
跟 obj.print
是一模一樣的 function,然而印出來的東西卻完全不一樣。
那是因為在執行 obj.print()
的時候,print
function 知道他是被 obj
呼叫,因此當中的 this 會指向 obj
。要怎麼知道誰呼叫誰呢?看那個 .
就對了!所以 obj.print()
的意思就是 obj
呼叫 print
function 並執行
而當我們在執行 fn()
的時候,因為沒有那個 .
,所以找不到可以指向的對象,在找不到的情況下,就會直接指向 window
!
const obj = {
value: 18,
tool: {
value: 81,
print: function() {console.log(this.value)}
}
}
let a = obj.tool
let b = obj.tool.print
obj.tool.print() // 81
a.print() // 81
b() // undefined
根據同樣的邏輯,往前找是誰呼叫了這個 function,所以在 obj.tool.print()
這個例子,是 tool
呼叫了 print
,所以值是 81。
a.print()
這個例子是 a
呼叫了 print
,而 a
本身就是 tool
,所以結果一樣是 81。
最後,雖然 b
本身就是 print
,但是因為找不到呼叫的對象,所以 this
會指向 window
,而因為 window
當中找不到 value
這個值,所以得到的結果是 undefined。
const obj = {
value: 18,
print: function () {
function c () {
console.log(this)
}
c()
}
}
obj.print() // window
在這個 case 當中,print
function 裡面包含了另外一個 c
function,而且這個 c
會直接執行。雖然看起來是因為 print
執行之後 c
跟著執行,所以好像是 print
呼叫了他。但回到剛剛的規則上,c
其實找不到是誰呼叫他的(前面沒有 .
),所以在 c
當中的 this
就是 window。
如果今天要讓 c
function 當中的 this
指向 obj
,可以怎麼做呢?
解法 1: 先把 this 存下來
const obj = {
value: 18,
print: function () {
let self = this
function c () {
console.log(self)
}
c()
}
}
obj.print() // {value: 18, print: [Function]}
解法 2: 變成 IIFE,直接把 this 給傳進去
const obj = {
value: 18,
print: function () {
(function c (self) {
console.log(self)
})(this)
}
}
obj.print() // {value: 18, print: [Function]}
解法 3: 使用 arrow function
const obj = {
value: 18,
print: function () {
const c = () => console.log(this)
c()
}
}
obj.print() // {value: 18, print: [Function]}
arrow function 的出現使得規則好像變得不太一樣了?沒錯,Arrow function 當中的 this,會跟「它被宣告的地方」有關,而不是跟「它被呼叫的對象」有關。所以因為這裡我們是在 obj
裡面宣告 c
,所以 c
當中的 this
就會指向 obj
。
解法 4: 使用 call, apply, bind
// call
const obj = {
value: 18,
print: function () {
function c(){
console.log(this)
}
c.call(this)
}
}
obj.print() // {value: 18, print: [Function]}
// apply
const obj = {
value: 18,
print: function () {
function c(){
console.log(this)
}
c.apply(this)
}
}
obj.print() // {value: 18, print: [Function]}
// bind
const obj = {
value: 18,
print: function () {
function c(){
console.log(this)
}
const d = c.bind(this)
d()
}
}
obj.print() // {value: 18, print: [Function]}
使用 JavaScript 當中的 call
, apply
, bind
方法,綁定 function 當中的 this
。三者的差異在於:
call
: 綁定 this
並可以傳入引數apply
: 跟 call
一樣,只是用陣列的方式傳入引數bind
: 永久綁定 this
。使用的時候會產生新的 function,因此需要傳出來使用。或者當下直接執行。以剛剛的例子來說,就是// 傳出來使用
const d = c.bind(this)
d() // {value: 18, print: [Function]}
// 直接執行
c.bind(this)() // {value: 18, print: [Function]}
希望看完以上的範例之後,下次看到 this 就不會再那麼害怕。不過就算當下不確定,還是可以隨時用 console.log 把 this 印出來看看。今天就簡單先到這邊,我們明天見囉!
TD
Be curious as astronomer, think as physicist, hack as engineer, fight as baseball player
More about me"Life is like riding a bicycle. To keep your balance, you must keep moving."